Skip to content

fix: apply custom Map results when mapping to existing target (#900)#970

Open
leno23 wants to merge 1 commit into
MapsterMapper:developmentfrom
leno23:fix/getter-only-maptotarget-900-dev
Open

fix: apply custom Map results when mapping to existing target (#900)#970
leno23 wants to merge 1 commit into
MapsterMapper:developmentfrom
leno23:fix/getter-only-maptotarget-900-dev

Conversation

@leno23
Copy link
Copy Markdown

@leno23 leno23 commented May 25, 2026

Summary

  • Fixes MapToTarget (mapper.Map(dto, existingEntity)) ignoring custom .Map() resolvers when the destination member already has a value.
  • When a destination member has an explicit custom .Map() configuration, skip passing the existing destination member into nested adaptation so the resolver result is assigned directly.
  • Adds regression tests reproducing issue Mapping an object with getter only properties seems to be broken in v10.0.6 (works as expected in 7.4.0) #900 for MapToTarget with pre-existing destination values, null destination members, and new-object mapping.

Root cause

For MapToTarget, ClassAdapter always passed the existing destination member expression into CreateAdaptExpression. That triggered nested MapToTarget adaptation for the member type. For immutable/getter-only types (like primary-constructor classes), nested mapping preserves the existing destination instance instead of replacing it with the custom .Map() result.

Test plan

  • CI passes on Mapster.Tests
  • WhenMappingGetterOnlyMapToTargetRegression.MapToTarget_WithCustomMap_ShouldReplaceExistingDestinationMemberValue
  • Existing MapToTarget regression tests remain green

Fixes #900

…rMapper#900)

MapToTarget was passing the existing destination member into nested
adaptation even when a custom .Map() resolver already computed the final
value, causing immutable/getter-only member values to be preserved.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment on lines +33 to +34
config.NewConfig<Dto900, Entity900>()
.Map(x => x.Data, x => x.Data == null ? null : new Data900(x.Data.Value));
Copy link
Copy Markdown
Contributor

@DocSvartz DocSvartz May 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andrerav @stagep
I think users want something like this.

 config.NewConfig<Poco, Dto>()
     .Map(dto => dto.Age, src => src.AgeString)
     .OvverideTypesConfigForMapPair(cfg=> 
     cfg.DefaultValue("42"));

But Map doesn't override the type mapping behavior; it can simply provide another alternative source.

Example For this case:

config.NewConfig<Dto900, Entity900>()
            .Map(x => x.Data, x => x.Data ) 
.OvverideTypesConfigForMapPair(cfg=> 
     cfg.MapWith(x => x.Data == null ? null : new Data900(x.Data.Value));

@leno23
Copy link
Copy Markdown
Author

leno23 commented May 27, 2026

Fixes #900 — custom .Map(...) results now apply on MapToTarget / getter-only destinations (ClassAdapter + BaseClassAdapter). Branch rebased on latest development. Awaiting workflow approval for CI.

@leno23
Copy link
Copy Markdown
Author

leno23 commented May 27, 2026

Thanks @DocSvartzOverrideTypesConfigForMapPair (or similar) would be a cleaner long-term API for overriding nested type-mapping behavior from a member .Map.

This PR stays intentionally minimal: #900 reports that an explicit .Map(x => x.Data, …) is ignored on MapToTarget because ClassAdapter always prefers UseDestinationValue/destination member when MapType.MapToTarget, even when a custom resolver exists. The fix only skips destination-member reuse when a custom map resolver is present (HasCustomMemberMap).

Happy to pivot to a config-override API if maintainers prefer that direction — the regression tests should still apply either way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants